/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.actions;

import com.softwarecraftsmen.orogeny.actions.compilation.BuildLogDiagnosticListener;
import com.softwarecraftsmen.orogeny.actions.compilation.BuildLogWriter;
import com.softwarecraftsmen.orogeny.actions.compilation.JavaSourceCompiler;
import com.softwarecraftsmen.orogeny.buildEnvironments.BuildEnvironment;
import com.softwarecraftsmen.orogeny.buildLogs.BuildLog;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.AllDetails;
import static com.softwarecraftsmen.orogeny.buildLogs.Verbosity.Summary;
import com.softwarecraftsmen.orogeny.execution.SuccessOrFailure;
import static com.softwarecraftsmen.orogeny.execution.SuccessOrFailure.Failure;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectories;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectories.absoluteDirectories;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectoriesAndFiles.absoluteDirectoriesAndFiles;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory;
import com.softwarecraftsmen.orogeny.filing.AbsoluteFile;
import com.softwarecraftsmen.orogeny.filing.AbsolutePath;
import com.softwarecraftsmen.orogeny.filing.AbsolutePaths;
import com.softwarecraftsmen.orogeny.filing.CouldNotMakeAllFoldersNecessaryException;
import com.softwarecraftsmen.orogeny.filing.IsNotADirectoryException;
import static com.softwarecraftsmen.orogeny.filing.findFileFilters.AbstractFindFilesFilter.JavaSource;
import com.softwarecraftsmen.orogeny.filing.findFileFilters.FindFilesFilter;
import org.jetbrains.annotations.NotNull;

import javax.annotation.processing.Processor;
import static java.lang.String.format;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import java.util.Collections;
import java.util.List;
import static java.util.Locale.UK;
import java.util.Set;

public class CompileAction extends AbstractAction
{
	private final AbsoluteDirectories sourceDirectories;
	private final FindFilesFilter findFilesFilter;
	private final AbsolutePaths classPath;
	private final AbsoluteDirectory outputDirectory;
	private final AbsoluteDirectory generatedSourceOutputDirectory;
	private final List<Processor> annotationProcessors;

	private CompileAction(final @NotNull AbsoluteDirectories sourceDirectories, final @NotNull FindFilesFilter sourceFileFilter, final @NotNull AbsolutePaths classPath, final @NotNull AbsoluteDirectory outputDirectory, final @NotNull AbsoluteDirectory generatedSourceOutputDirectory, final @NotNull List<Processor> annotationProcessors)
	{
		this.sourceDirectories = sourceDirectories;
		this.findFilesFilter = sourceFileFilter;
		this.classPath = classPath;
		this.outputDirectory = outputDirectory;
		this.generatedSourceOutputDirectory = generatedSourceOutputDirectory;
		this.annotationProcessors = annotationProcessors;
	}

	@NotNull
	public String describeWhatWeDo()
	{
		return "compile";
	}

	@NotNull
	public SuccessOrFailure execute(final @NotNull BuildLog buildLog, final @NotNull BuildEnvironment buildEnvironment)
	{
		if (!sourceDirectories.exists())
		{
			return Failure(format(UK, "One or more of the sourceDirectories %1$s does not exist", sourceDirectories));
		}
		final Set<AbsoluteFile> sourceFiles = sourceDirectories.findFiles(findFilesFilter);
		if (sourceFiles.isEmpty())
		{
			return Failure(format(UK, "The sourceDirectories %1$s once filtered by %2$s contain no source files", sourceDirectories, findFilesFilter));
		}
		final JavaSourceCompiler javaSourceCompiler = new JavaSourceCompiler(classPath, outputDirectory, sourceFiles, generatedSourceOutputDirectory, annotationProcessors);

		if (!classPath.exists())
		{
			return Failure(format(UK, "The classPath %1$s does not exist", classPath));
		}

		try
		{
			outputDirectory.makeAllFoldersNecessary();
		}
		catch (IsNotADirectoryException e)
		{
			return Failure(e);
		}
		catch (CouldNotMakeAllFoldersNecessaryException e)
		{
			return Failure(e);
		}

		buildLog.writeMessage(Summary, format(UK, "About to compile to '%1$s' using class path '%2$s'", outputDirectory, classPath));
		for (AbsoluteFile sourceFile : sourceFiles)
		{
			buildLog.writeMessage(AllDetails, format(UK, "Including source %1$s", sourceFile));
		}
		final SuccessOrFailure successOrFailure = javaSourceCompiler.compile(new BuildLogDiagnosticListener(buildLog), new BuildLogWriter(buildLog));
		if (successOrFailure.isFailure())
		{
			return successOrFailure;
		}
		return successOrFailure;
	}

	@NotNull
	public SpecifyAnnotationProcessors processAnnotationsWith(final @NotNull Processor... annotationProcessors)
	{
		return new SpecifyAnnotationProcessors(asList(annotationProcessors));
	}

	public class SpecifyAnnotationProcessors
	{
		private final List<Processor> annotationProcessors;

		private SpecifyAnnotationProcessors(final @NotNull List<Processor> annotationProcessors)
		{
			this.annotationProcessors = new ArrayList<Processor>(CompileAction.this.annotationProcessors){{ addAll(annotationProcessors); }};
		}

		@NotNull
		public CompileAction generatingSourceTo(final @NotNull AbsoluteDirectory generatedSourceOutputDirectory)
		{
			return new CompileAction(sourceDirectories, findFilesFilter, classPath, outputDirectory, generatedSourceOutputDirectory, annotationProcessors);
		}
	}

	@NotNull
	public static SpecifySourceDirectories compile(final @NotNull AbsoluteDirectory... sourceDirectories)
	{
		return new SpecifySourceDirectories(absoluteDirectories(sourceDirectories));
	}

	@NotNull
	public static SpecifySourceDirectories compile(final @NotNull AbsoluteDirectories sourceDirectories)
	{
		return new SpecifySourceDirectories(sourceDirectories);
	}

	public static final class SpecifySourceDirectories
	{
		private static final AbsolutePaths[] EmptyClassPath = new AbsolutePaths[]{};
		private final AbsoluteDirectories sourceDirectories;

		private SpecifySourceDirectories(final @NotNull AbsoluteDirectories sourceDirectories)
		{
			this.sourceDirectories = sourceDirectories;
		}

		@NotNull
		public SpecifyClassPath withJdkClassPath()
		{
			return usingClassPath(EmptyClassPath);
		}

		@NotNull
		public SpecifyClassPath usingClassPath()
		{
			return usingClassPath(EmptyClassPath);
		}

		@NotNull
		public SpecifyClassPath usingClassPath(final @NotNull AbsolutePath... classPath)
		{
			return usingClassPath(absoluteDirectoriesAndFiles(classPath));
		}

		@NotNull
		public SpecifyClassPath usingClassPath(final @NotNull AbsolutePaths... classPath)
		{
			return new SpecifyClassPath(absoluteDirectoriesAndFiles(classPath));
		}

		public final class SpecifyClassPath
		{
			private final AbsolutePaths classPath;

			private SpecifyClassPath(final @NotNull AbsolutePaths classPath)
			{
				this.classPath = classPath;
			}

			@NotNull
			public CompileAction toOutputDirectory(final @NotNull AbsoluteDirectory outputDirectory)
			{
				return new CompileAction(sourceDirectories, JavaSource, classPath, outputDirectory, outputDirectory, Collections.<Processor>emptyList());
			}
		}
	}
}
